package com.csaba.connector.bha;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Currency;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.csaba.connector.model.AbstractRemoteObject;
import com.csaba.connector.model.Amount;
import com.csaba.util.LoggerHelper;
import com.ind.simplenode.SimpleNode;

public class SimpleNodeHelper
{
	private final static Logger logger = LoggerHelper.getDefaultLogger();
	private static final HashSet<String> PROTECTED = new HashSet<String>();
	static
	{
		PROTECTED.add("credential");
	}

	////////////// DATE

	private static final ThreadLocal<DateFormat> dateFactory = new ThreadLocal<DateFormat>()
	{
		@Override
		protected DateFormat initialValue()
		{
			return new SimpleDateFormat("yyyyMMddHHmmSS");
		}
	};

	public final static DateFormat getShortDateFormat()
	{
		return dateFactory.get();
	}

	public static void loadProperties( final AbstractRemoteObject object, final SimpleNode properties )
	{
		final String[] fieldNames = getPropertyNames(properties);
		for ( final String name : fieldNames )
		{
			final String value = getProperty(properties, name);
			if ( value == null )
				continue;

			//detect boolean value
			if ( value.equals("true") )
				object.setRemoteProperty(name, Boolean.TRUE);
			if ( value.equals("false") )
				object.setRemoteProperty(name, Boolean.FALSE);
			else
				object.setRemoteProperty(name, value);
		}
	}

	public static void setDateProperty( final AbstractRemoteObject object, final SimpleNode node, final String name )
	{
		try
		{
			final String value = getAndRemove(node, name);
			object.setRemoteProperty(name, getShortDateFormat().parse(value));
		}
		catch ( final Exception e )
		{
			logger.fine("Something wrong with a property: " + name + "(" + e.getMessage() + ")");
		}
	}

	public static void setAmountProperty( final AbstractRemoteObject object, final SimpleNode node, final String name,
			final Currency currency )
	{
		try
		{
			final String value = getAndRemove(node, name);
			object.setRemoteProperty(name, new Amount(Double.valueOf(value), currency));
		}
		catch ( final Exception e )
		{
			logger.fine("Something wrong with a property: " + name + "(" + e.getMessage() + ")");
		}
	}

	public static void setBooleanProperty( final AbstractRemoteObject object, final SimpleNode node, final String name )
	{
		try
		{
			final String value = getAndRemove(node, name);
			object.setRemoteProperty(name, Boolean.valueOf(value));
		}
		catch ( final Exception e )
		{
			logger.fine("Something wrong with a property: " + name + "(" + e.getMessage() + ")");
		}
	}

	public static void setProperty( final AbstractRemoteObject object, final SimpleNode node, final String name )
	{
		try
		{
			final String value = getAndRemove(node, name);
			object.setRemoteProperty(name, value);
		}
		catch ( final Exception e )
		{
			logger.fine("Something wrong with a property: " + name + "(" + e.getMessage() + ")");
		}
	}

	public static void setPhoneProperty( final AbstractRemoteObject object, final SimpleNode node, final String name )
	{
		try
		{
			final String country = getAndRemove(node, name + ".country");
			final String number = getAndRemove(node, name + ".number");
			final String region = getAndRemove(node, name + ".region");
			object.setRemoteProperty(name, "+" + country + "-" + region + "-" + number);
		}
		catch ( final Exception e )
		{
			logger.fine("Something wrong with a property: " + name + "(" + e.getMessage() + ")");
		}
	}

	public static void dumpNode( final String label, final SimpleNode node )
	{
		logger.fine(" >>>>> DUMP STARTS: " + label + " <<<<<");
		dumpNode(Level.FINE, "", node);
		logger.fine(" <<<<< DUMP ENDS: " + label + " >>>>>");
	}

	@SuppressWarnings( "unchecked" )
	private static void dumpNode( final Level level, final String path, final SimpleNode node )
	{
		final Hashtable<String, Object> map = node.getNodeMap();
		for ( final String key : map.keySet() )
		{
			final Object value = map.get(key);
			if ( value instanceof Vector )
			{
				final Vector<Object> list = (Vector<Object>) value;
				int i = 0;
				for ( final Object listElement : list )
				{
					if ( listElement instanceof String )
					{
						logger.log(level, path + "." + key + "[" + i + "] = '" + protectDump(key, (String) listElement)
								+ "'");
					}
					else if ( listElement instanceof SimpleNode )
					{
						dumpNode(level, path + "." + key + "[" + i + "]", (SimpleNode) listElement);
					}
					else
					{
						logger.severe("Invalid array content. Path=" + path + "." + key + "[" + i + "] Class="
								+ listElement.getClass().getName());
					}

					i++;
				}
			}
			else if ( value instanceof SimpleNode )
			{
				dumpNode(level, path + "." + key, (SimpleNode) value);
			}
			else if ( value instanceof String )
			{
				logger.log(level, path + "." + key + " = '" + protectDump(key, (String) value) + "'");
			}
			else
			{
				logger.severe("Invalid node content. Path=" + path + "." + key + " Class=" + value.getClass().getName());
			}
		}
	}

	private static String protectDump( final String key, final String value )
	{

		return PROTECTED.contains(key) ? "< censored >" : value;
	}

	public static String getProperty( final SimpleNode properties, final String field )
	{
		String property = null;
		SimpleNode node = properties;
		String fieldPath = field;
		while ( fieldPath.contains(".") )
		{
			final String pathElement = fieldPath.substring(0, fieldPath.indexOf("."));
			fieldPath = fieldPath.substring(fieldPath.indexOf(".") + 1);
			final int count = node.numberOfElements(pathElement);
			if ( count == 0 )
				throw new IllegalArgumentException("Unsupported property name: " + field);
			else if ( count > 1 ) //TODO extend "path API" for multiple values
				throw new IllegalArgumentException("Path access is not possible for array values: " + pathElement
						+ " (" + field + ")");

			final Object value = node.get(pathElement);
			if ( !( value instanceof SimpleNode ) )
				throw new IllegalArgumentException("Illegal path element. Element is not a node: " + pathElement + " ("
						+ field + ")");
			node = (SimpleNode) value;
		}
		property = node.getString(fieldPath);
		if ( property == null )
			throw new IllegalArgumentException("Unsupported property name: " + field);
		return property;
	}

	public static String getAndRemove( final SimpleNode properties, final String field )
	{
		return getAndRemoveInner(properties, field, field);
	}

	private static String getAndRemoveInner( final SimpleNode properties, final String field, final String fullName )
	{
		String property = null;
		if ( field.contains(".") )
		{
			final String nodeName = field.substring(0, field.indexOf("."));
			final int count = properties.numberOfElements(nodeName);
			if ( count == 0 )
				return null;
			else if ( count > 1 ) //TODO extend "path API" for multiple values
				throw new IllegalArgumentException("Path access is not possible for array values: " + nodeName + " ("
						+ fullName + ")");

			final Object node = properties.get(nodeName);
			if ( !( node instanceof SimpleNode ) )
				throw new IllegalArgumentException("Illegal path element. Element is not a node: " + nodeName + " ("
						+ fullName + ")");
			final SimpleNode simpleNode = (SimpleNode) node;
			property = getAndRemoveInner(simpleNode, field.substring(field.indexOf(".") + 1), fullName);

			if ( simpleNode.getNodeMap().size() < 1 && properties.contains(nodeName) )
				properties.remove(nodeName);
		}
		else
		{
			property = properties.getString(field);
			if ( property != null )
				properties.remove(field);
		}
		return property;
	}

	public static String[] getPropertyNames( final SimpleNode properties )
	{
		final ArrayList<String> fieldNames = new ArrayList<String>();

		addFields(properties, null, fieldNames);

		return fieldNames.toArray(new String[fieldNames.size()]);
	}

	@SuppressWarnings( "unchecked" )
	private static void addFields( final SimpleNode node, String path, final ArrayList<String> fieldNames )
	{
		final Set<String> keys = node.getNodeMap().keySet();
		if ( path == null )
			path = "";
		else
			path = path + ".";
		for ( final String key : keys )
		{
			final Object value = node.get(key); //if a node array is hit, only the first element is enumerated
			if ( value instanceof SimpleNode )
				addFields((SimpleNode) value, path + key, fieldNames);
			else
				fieldNames.add(path + key);
		}
	}
}
